package org.springframework.web.servlet;

import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController;
import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestMethod;
import top.malaoshi.easy.logging.Log;
import top.malaoshi.easy.logging.LogFactory;
import top.malaoshi.sprout.ioc.DefaultSingletonBeanRegistry;
import top.malaoshi.sprout.validation.Validator;
import top.malaoshi.util.Out;
import top.malaoshi.web.utils.StaticFileAndDirectory;
import top.malaoshi.webmvc.*;
import top.malaoshi.webmvc.exception.HttpRequestMethodNotSupportedException;
import top.malaoshi.webmvc.exception.NotFoundException;
import top.malaoshi.webmvc.info.MethodInfo;
import top.malaoshi.webmvc.info.MethodsArgumentInfo;
import top.malaoshi.webmvc.info.RequestMappingInfo;
import top.malaoshi.webmvc.parameter.ExceptionHandlerResolver;
import top.malaoshi.webmvc.parameter.RequestParamAndMethodArgsResolver;
import top.malaoshi.webmvc.utils.MethodUtil;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;

/**
 * 自定义DispatcherServlet，用于拦截请求，做业务处理
 */
public class DispatcherServlet extends HttpServlet {

    static final Log LOG = LogFactory.getLog(DispatcherServlet.class);
    private WebApplicationStarter webApplication = new WebApplicationStarter();
    private ExceptionHandlerMethodResolver exceptionHandlerMethodResolver = new ExceptionHandlerMethodResolver();

    private ApplicationContext ac = new AnnotationConfigApplicationContext2();
    /**
     * 默认Servlet的工具类，如果是静态文件，交由该 默认Servlet 处理
     */
    DefaultServletHttpRequestHandler defaultRequestHandler = new DefaultServletHttpRequestHandler();

    DefaultSingletonBeanRegistry registry = DefaultSingletonBeanRegistry.me();

    /**
     * 默认处理错误，如：404、500，controller抛错
     */
    BasicErrorController basicErrorController = new BasicErrorController();

    {
        GlobalVariable.me();
    }
    /**
     * 覆盖父类的init方法，做一些初始化内容
     *
     * @throws ServletException
     */
    @Override
    public void init() throws ServletException {
        // 默认Servlet的工具类，必须调用此方法，否则其他方法无法调用
        defaultRequestHandler.setServletContext(getServletContext());

        // 扫描包路径
        String packagePath = this.getInitParameter("scanPackage");
        // 是否打印路由
        String printRoute = this.getInitParameter("printRoute");
        // 是否打印bean名
        String printBeanName = this.getInitParameter("printBeanName");
        boolean printRouteBool = false;
        if(printRoute!=null){
            printRouteBool = Boolean.parseBoolean(printRoute);
        }
        boolean printBeanNameBool = false;
        if(printBeanName!=null){
            printBeanNameBool = Boolean.parseBoolean(printBeanName);
        }


        long stime = System.currentTimeMillis();


        try {
            ac.execute(packagePath);
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }

        try {
            webApplication.start();
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
        try {
            exceptionHandlerMethodResolver.resolve();
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
        if(printRouteBool){
            Out.println("所有路由：----------");
            webApplication.getUrlMapping().forEach( (url, methodInfo) -> {
                Out.sep("").println(url," : ",methodInfo.getMethod().getDeclaringClass().getName(),".",methodInfo.getMethod().getName(),"()");
            });
            Out.println("-----------------");
        }
        if(printBeanNameBool){
            Out.println("所有Bean名字：------------");
            try {
                registry.forEachSingletonObjects( (name,obj)->{
                    Out.sep("").println(name," : ",obj.getClass().getName());
                });
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
            Out.println("-------------");
        }

        long etime = System.currentTimeMillis();
        Out.sep(" ").println("启动耗时：",etime-stime,"ms");
    }
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doRequest(req,resp,RequestMethod.GET);
    }

//    @Override
//    protected void doHead(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//        doRequest(req,resp,RequestMethod.HEAD);
//    }

    @Override
    protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doRequest(req,resp,RequestMethod.PUT);
    }

    @Override
    protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doRequest(req,resp,RequestMethod.DELETE);
    }

//    @Override
//    protected void doOptions(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//        doRequest(req,resp,RequestMethod.OPTIONS);
//    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doRequest(req,resp,RequestMethod.POST);
    }

    /**
     * 预处理异常，将相关信息放入到request属性里
     * @param req
     */
    private void preException(HttpServletRequest req,int status){
        // 设置错误码
        req.setAttribute("javax.servlet.error.status_code", status);
    }
    /**
     * 实现发请求的方法
     * @param req
     * @param resp
     * @param requestMethod 校验请求方法是否允许
     * @throws ServletException
     * @throws IOException
     */
    private void doRequest0(HttpServletRequest req, HttpServletResponse resp,RequestMethod requestMethod) throws Exception {
        //获取请求的url
        String url = req.getServletPath();

        // 如果是静态文件，交由默认Servlet处理
        boolean res = StaticFileAndDirectory.is(url);
        if(res) {
            // 用默认Servlet 处理
            defaultRequestHandler.handleRequest(req,resp);
            return;
        }
        //查找url对应的method对象
        MethodInfo methodInfo = webApplication.getUrlMapping().get(url);
        // 如果找不到匹配的方法
        if (null == methodInfo) {
            preException(req,HttpStatus.NOT_FOUND.value());
            throw new NotFoundException("请求资源["+url+"]不可用");
        }

        //获取method对应class的bean实例
        Object bean = registry.getSingletonObject(methodInfo.getBeanName());
        if (null == bean) {
            preException(req,HttpStatus.INTERNAL_SERVER_ERROR.value());
            throw new Exception("找不到类："+methodInfo.getMethod().getDeclaringClass().getName());

        }

        RequestMappingInfo requestMappingInfo = methodInfo.getRequestMappingInfo();
        boolean checkMethod = requestMappingInfo.checkMethod(requestMethod);
        if(!checkMethod){// 如果不允许就抛出异常
            String msg = String.format("不支持 '%s' 请求方法", requestMethod);
            preException(req,HttpStatus.METHOD_NOT_ALLOWED.value());
            throw new HttpRequestMethodNotSupportedException(msg);

        }

        //从request中解析方法需要的参数
        Object[] args = null;
        try {
            args = resolveParameter(req, resp, methodInfo);
        }catch (Exception e){
            try {
                boolean b = invokeExceptionHandler(methodInfo.getBeanName(), e, req, resp);
                // 没有符合的 ExceptionHandler，就抛出异常，由 ErrorController 处理
                if(!b){
                    preException(req,HttpStatus.INTERNAL_SERVER_ERROR.value());
                    throw e;
                }
                return;
            }catch (Exception e2) {
                e2.printStackTrace();
                // 如果执行 ExceptionHandler 抛异常，就抛出原异常，，由 ErrorController 处理
                preException(req,HttpStatus.INTERNAL_SERVER_ERROR.value());
                throw e;
            }
        }
        Method method = methodInfo.getMethod();
        try {
            List list = registry.getSingletonsByType(Validator.class);
            Validator validator = null;
            if(list!=null && !list.isEmpty()){
                validator = (Validator) list.get(0);
            }
            if(validator != null) {
                validator.validate(bean, method, args);
            }
        }catch (Exception e){
            try {
                boolean b = invokeExceptionHandler(methodInfo.getBeanName(), e, req, resp);
                // 没有符合的 ExceptionHandler，就抛出异常，由 ErrorController 处理
                if(!b){
                    preException(req,HttpStatus.INTERNAL_SERVER_ERROR.value());
                    throw e;
                }
                return;
            }catch (Exception e2) {
                e2.printStackTrace();
                // 如果执行 ExceptionHandler 抛异常，就抛出原异常，，由 ErrorController 处理
                preException(req,HttpStatus.INTERNAL_SERVER_ERROR.value());
                throw e;
            }
        }
        // 调用Controller的方法
        Object result = null;
        try {
            //执行反射调用
            result = method.invoke(bean, args);
        } catch (InvocationTargetException e) {
            try {
                Throwable throwable = e.getTargetException();
                if( throwable instanceof Exception) {
                    boolean b = invokeExceptionHandler(methodInfo.getBeanName(), (Exception) throwable, req, resp);
                    // 没有符合的 ExceptionHandler，就抛出异常，由 ErrorController 处理
                    if(!b){
                        preException(req,HttpStatus.INTERNAL_SERVER_ERROR.value());
                        throw e;
                    }
                }else{
                    throw e;
                }
                return;
            }catch (Exception e2) {
                e2.printStackTrace();
                // 如果执行 ExceptionHandler 抛异常，就抛出原异常，，由 ErrorController 处理
                preException(req,HttpStatus.INTERNAL_SERVER_ERROR.value());
                // 反射调用方法，该方法抛出自定义异常，通过下面方法取出该异常
                Throwable throwable = e.getTargetException();
                if( throwable instanceof Exception) {
                    Exception methodThrowException = (Exception)throwable;
                    throw methodThrowException;
                }else{
                    throw e;
                }

            }

        }catch (Exception e) {
            try {
                boolean b = invokeExceptionHandler(methodInfo.getBeanName(), e, req, resp);
                // 没有符合的 ExceptionHandler，就抛出异常，由 ErrorController 处理
                if(!b){
                    preException(req,HttpStatus.INTERNAL_SERVER_ERROR.value());
                    throw e;
                }
                return;
            }catch (Exception e2) {
                e2.printStackTrace();
                // 如果执行 ExceptionHandler 抛异常，就抛出原异常，，由 ErrorController 处理
                preException(req,HttpStatus.INTERNAL_SERVER_ERROR.value());
                throw e;
            }
        }
        // 处理返回结果
        if(result!=null && methodInfo.getResponseHandler()!=null) {
            methodInfo.getResponseHandler().handle(resp, result);
        }
    }


    /**
     * 如果当前 Controller 中，有 ExceptionHandler 注解修饰的方法，且异常一致，就调用该方法
     * 如果没有，就调用 @ControllerAdvice 或 @RestControllerAdvice 修饰的类，且该类中有 ExceptionHandler 修饰的方法，且异常一致，那么就调用
     * 只要抛错就退出方法
     *
     * @param beanName
     * @param e
     * @param req
     * @param resp
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     *
     * @return true：表示执行ExceptionHandler；false：表示没有符合的 ExceptionHandler ，即：没有执行
     */
    private boolean invokeExceptionHandler(String beanName, Exception e, HttpServletRequest req, HttpServletResponse resp)
            throws InvocationTargetException, IllegalAccessException,IOException {

        boolean isExecuteExceptionHandler = false;
        req.setAttribute(ExceptionHandlerResolver.REQUEST_EXCEPTION, e);

        List<MethodInfo> exceptionHandlerMethodInfos = exceptionHandlerMethodResolver.getByBeanNameAndException(beanName, e);
        if(exceptionHandlerMethodInfos!=null && !exceptionHandlerMethodInfos.isEmpty()){
            isExecuteExceptionHandler = true;
            invokeExceptionHandler0(exceptionHandlerMethodInfos, req, resp);
        }else{
            List<MethodInfo> controllerAdviceAndException = exceptionHandlerMethodResolver.getByControllerAdviceAndException(e);

            if(controllerAdviceAndException!=null && !controllerAdviceAndException.isEmpty()) {
                isExecuteExceptionHandler = true;
                invokeExceptionHandler0(controllerAdviceAndException, req, resp);
            }
        }
        req.removeAttribute(ExceptionHandlerResolver.REQUEST_EXCEPTION);
        return isExecuteExceptionHandler;
    }
    private void invokeExceptionHandler0( List<MethodInfo> controllerAdviceAndException , HttpServletRequest req, HttpServletResponse resp)
            throws InvocationTargetException,IllegalAccessException, IOException{
        MethodInfo exceptionHandlerMethod = controllerAdviceAndException.get(0);
        Object bean = registry.getBeanByName(exceptionHandlerMethod.getBeanName());
        Object[] methodArgs = resolveParameter(req, resp, exceptionHandlerMethod);
        Method method = exceptionHandlerMethod.getMethod();
        method.setAccessible(true);
        Object result = null;
        try {
            result = method.invoke(bean, methodArgs);
        } catch (IllegalArgumentException e){
            String methodName = MethodUtil.getAllNameOfMethod(method);
            LOG.error("无法解析参数："+methodName,e);
            throw e;
        }
        // 处理返回结果
        if(result!=null && exceptionHandlerMethod.getResponseHandler()!=null) {
            exceptionHandlerMethod.getResponseHandler().handle(resp, result);
        }

    }
    /**
     * 实现发请求的方法
     * @param req
     * @param resp
     * @param requestMethod 校验请求方法是否允许
     * @throws ServletException
     * @throws IOException
     */
    private void doRequest(HttpServletRequest req, HttpServletResponse resp,RequestMethod requestMethod) throws ServletException, IOException {
        try{
            doRequest0(req,resp,requestMethod);
        }catch (Exception e){
            try {

                List errorControllers = registry.getSingletonsByType(ErrorController.class);
                if(errorControllers!=null && !errorControllers.isEmpty() ) {
                    // 只调用第一个 ErrorController
                    ErrorController errorController = (ErrorController)errorControllers.get(0);
                    Method method = errorController.getClass().getMethod("error", HttpServletRequest.class, HttpServletResponse.class, Exception.class);
                    method.invoke(errorController, req, resp, e);
                }else{
                    basicErrorController.error(req,resp,e);
                }
            }catch (Exception ex){
                throw new RuntimeException(ex);
            }
        }

    }
    /**
     * 从request对象中，解析 方法中需要的参数
     * @param req
     * @param resp
     * @param methodInfo
     * @return
     */
    private Object[] resolveParameter(HttpServletRequest req, HttpServletResponse resp, MethodInfo methodInfo){
        List<RequestParamAndMethodArgsResolver> argumentResolvers = methodInfo.getArgumentResolvers();
        int size = argumentResolvers.size();
        Object[] args = new Object[size];
        for(int i = 0 ; i<size ; i++){
            RequestParamAndMethodArgsResolver item = argumentResolvers.get(i);
            MethodsArgumentInfo mai = methodInfo.getMethodsArgumentInfos().get(i);
            args[i] = item.resolver(req,resp,mai);

        }
        return args;
    }


    /**
     * 从当前Object解析出有ExceptionHandler注解修饰的方法，
     * 并且抛出的异常是该注解配置的异常的子类
     * 此时调用该方法处理异常
     */
    private void invokeExceptionHandlerMethod(Object object,Exception e) throws InvocationTargetException, IllegalAccessException {
//        Method method = resolveByExceptionHandlerAndException(object, e);
//        method.invoke(object,e);
    }
}

